/*  ----------------------------------------------------------------------------
 *  Arello Mobile
 *  Fast 3d Engine
 *  ----------------------------------------------------------------------------
 * Except where otherwise noted, this work is licensed under
 * http://creativecommons.org/licenses/by/3.0
 */

#include "RenderList.h"
#include <Color.h>
#include <lib3d/Math_lib3d.h>
#include <lib3d/Light.h>
#include <logs/Log.h>
#include <lib3d/node/Camera.h>

namespace liba {
namespace lib3d {
namespace hard {

RenderList::RenderList()
{
	Clear();

	lights.reserve( 10 );
	models.reserve( 1000 );
#ifndef IPHONE_VERSION
	shadow_models.reserve( 1000 );
	shadow_lights.reserve( 10 );
#endif
	sprites.reserve( 1000 );
}

void RenderList::Clear()
{
	this->ambient_color = Color(0, 0, 0);
	this->clear_render_target = true;
	this->fog_range = 0;
	this->camera_projection = Matrix::identity();
	this->camera_world = Matrix::identity();
	this->view_mat = Matrix::identity();
	this->viewport = Viewport(-1, -1, -1, -1);
	this->fog_color = Color(0, 0, 0);
	this->perform_viewport_culling = true;
	this->lights.clear();
	this->models.clear();
#ifndef IPHONE_VERSION
	this->shadow_models.clear();
	this->shadow_lights.clear();
#endif
	this->sprites.clear();

	this->first_transparent_model = 0;
	this->render_order.clear();
}

void RenderList::calculate(int shadow_passes, int lighting_passes)
{
	view_mat = Matrix::invert(camera_world);
	
	// 1. make Shadow lights go first
	for( RenderLights::iterator lit = lights.begin(); lit != lights.end(); ++lit )
	{
		switch(lit->attenuation)
		{
		case Light::LINEAR:
			lit->range = lit->intensity;
			lit->clq = Vector(0,1,0);
			break;
		case Light::QUADRATIC:
			lit->range = sqrt(lit->intensity);
			lit->clq = Vector(0,0,1);
			break;
		default:
			lit->range = 1;
			lit->clq = Vector(1,0,0);
		}
	}

	RenderOrder transparent_order;
	
//	int models_out = 0;

	// 2. make Transparent models go last, and in order
	for( RenderModels::iterator mit = models.begin(); mit != models.end(); ++mit )
	{
		if(perform_viewport_culling && mit->bounding_sphere.get_radius() != 0)
		{
			Matrix mat = view_mat * mit->world;
			math::Sphere sphere = mit->bounding_sphere;
			sphere.transform(mat);

			if(frustrum.intersects(sphere) == math::Intersect::DISJOINT)
			{
//				models_out++;
				continue;
			}
		}
		
		if( !mit->material.is_transparent() )
		{
			//non transparent
			mit->unimportance = -1;
			
			RenderOrderItem item;
			item.is_sprite = false;
			item.unimportance = -1;

			item.vb = mit->vb;
			
			if(mit->material.diffuse_texture.texture)
				item.tex = mit->material.diffuse_texture.texture;
		else
				if(mit->material.emissive_texture.texture)
					item.tex = mit->material.emissive_texture.texture;
			else
				item.tex = 0;
			
			item.number = mit - models.begin();
			
			render_order.push_back(item);
			continue;
		}
		
//		printf("models out: %d\n", models_out);
		bool isOrthoProjection = lib3d::node::Camera::active_camera() ? lib3d::node::Camera::active_camera()->ortho_projection() : false;
		
		double length_to_camera = 0.0f;
		
		if(isOrthoProjection)
		{
			length_to_camera = fabs(mit->world.shift().z - camera_world.shift().z);
		}
		else
		{
			length_to_camera = mit->world.shift().length( camera_world.shift() );
		}
		
		mit->unimportance = 1/(1 + length_to_camera);
		
		RenderOrderItem item;
		item.is_sprite = false;
		item.unimportance = mit->unimportance;
		
		item.vb = mit->vb;
		
		if(mit->material.diffuse_texture.texture)
			item.tex = mit->material.diffuse_texture.texture;
		else
			if(mit->material.emissive_texture.texture)
				item.tex = mit->material.emissive_texture.texture;
			else
				item.tex = 0;
		
		item.number = mit - models.begin();
		
		transparent_order.push_back(item);
	}

	// make sprites go in order
	for( RenderSprites::iterator sit = sprites.begin(); sit != sprites.end(); ++sit )
	{
		double length_to_camera = sit->emitter.shift().length( camera_world.shift() );
		sit->unimportance = 1/(1 + length_to_camera);

		RenderOrderItem item;
		item.is_sprite = true;
		item.unimportance = sit->unimportance;
		
		item.vb = 0;
		
		if(sit->material.diffuse_texture.texture)
			item.tex = sit->material.diffuse_texture.texture;
		else
			if(sit->material.emissive_texture.texture)
				item.tex = sit->material.emissive_texture.texture;
			else
				item.tex = 0;

		item.number = sit - sprites.begin();
		
		transparent_order.push_back(item);		
	}

	std::sort( render_order.begin(), render_order.end() ); // do not scatter the models with the same vb
	std::sort( transparent_order.begin(), transparent_order.end() ); // do not scatter the models with the same vb
	first_transparent_model = render_order.size();

	render_order.insert(render_order.end(), transparent_order.begin(), transparent_order.end());
}

} // namespace hard
} // namespace lib3d
} // namespace liba

